#pragma once
#include <cstring>
#include <sys/epoll.h>
#include <unistd.h>
#include "socket.hpp"
/*
    多路转接之epoll
*/
class EpollServer
{
    const static int default_num = 10;
    const static int default_fd = -1;
    const static int default_timeout = 5000; // 单位ms

private:
    void dispatch(int n) // n代表就绪事件个数，此时遍历的都是合法且就绪的事件，没有任何多余的动作
    {
        for (int i = 0; i < n; i++)
        {
            if (_ev_arry[i].events & EPOLLIN) // 读事件检测
            {
                if (_ev_arry[i].data.fd == _listensocket->getsockfd())
                {
                    // 监听套接字的读事件就绪，此时有新的链接到来
                    // 1、获取新链接的fd
                    struct sockaddr_in srcaddr;
                    int newfd = _listensocket->Accept(&srcaddr);
                    if (newfd < 0)
                    {
                        LOG(WARNING, "listensocket accept error!\n");
                        return;
                    }
                    // 2、将其添加到epoll模型中
                    struct epoll_event ev;
                    ev.events = EPOLLIN;
                    // if(newfd == 5)
                    //     ev.events = EPOLLIN;
                    // else
                    //     ev.events = EPOLLIN | EPOLLET;
                    ev.data.fd = newfd;
                    epoll_ctl(_epoll_fd, EPOLL_CTL_ADD, newfd, &ev);
                    LOG(INFO, "epoll add fd:%d\n", newfd);
                }
                else
                {
                    // 普通文件描述符读事件就绪
                    char buf[1024] = {0};
                    int n = recv(_ev_arry[i].data.fd, buf, sizeof(buf)-1, 0);
                    if (n > 0)
                    {
                        LOG(INFO, "fd: %d, client say:%s\n", _ev_arry[i].data.fd, buf);
                        string echo_msg = "server echo:";
                        echo_msg += buf + string("\n");
                        send(_ev_arry[i].data.fd, echo_msg.c_str(), echo_msg.size(), 0);
                    }
                    else if (n == 0)
                    {
                        // 对方关闭链接
                        LOG(INFO, "client close, fd: %d\n", _ev_arry[i].data.fd);
                        //要从epoll模型中删除文件描述符，必须保证其是合法的
                        // 合法的意思就是：fd对应的内核数据结构体还在，所有必须先从epoll模型中移除，才能关闭fd
                        epoll_ctl(_epoll_fd, EPOLL_CTL_DEL, _ev_arry[i].data.fd, nullptr);
                        close(_ev_arry[i].data.fd);
                    }
                    else
                    {
                        // 读异常
                        LOG(INFO, "client error, fd: %d\n", _ev_arry[i].data.fd);
                        epoll_ctl(_epoll_fd, EPOLL_CTL_DEL, _ev_arry[i].data.fd, nullptr);
                        close(_ev_arry[i].data.fd);
                    }
                }
            }
        }
    }

public:
    EpollServer(uint16_t port) : _listensocket(make_unique<socket_ns::TcpSocket>()),
                                _port(port), _epoll_fd(default_fd), _timeout(default_timeout),
                                _ev_arry(new epoll_event[default_num]),
                                _is_running(true)
    {
    }

    void init() // 相比select和poll写法，加入了此函数
    {
        struct sockaddr_in localaddr;
        bzero(&localaddr, sizeof(localaddr));
        localaddr.sin_family = AF_INET;
        localaddr.sin_port = htons(_port);
        localaddr.sin_addr.s_addr = INADDR_ANY;

        // 1、创建套接字 2、绑定ip+port
        _listensocket->buildServerSocket((struct sockaddr *)&localaddr);

        // 3、创建epoll模型
        _epoll_fd = epoll_create(128);
        if (_epoll_fd < 0)
        {
            LOG(FATAL, "epoll fd create failed!\n");
            exit(1);
        }
        LOG(INFO, "epoll create success, fd:%d\n", _epoll_fd);

        // 4、将监听套接字加入到epoll模型中
        struct epoll_event ev;
        ev.events = EPOLLIN | EPOLLET; //epoll有两种工作模式，LT、ET，可以通过设置监听事件类型的方式针对不同的文件描述符进行设置
        ev.data.fd = _listensocket->getsockfd();
        epoll_ctl(_epoll_fd, EPOLL_CTL_ADD, _listensocket->getsockfd(), &ev);
        LOG(INFO, "epoll add fd:%d\n", _listensocket->getsockfd());
        // 为什么epoll_ctl中传入了要监听的文件描述符，而epoll_event中仍要设置fd
        // 因为epoll_event中的fd是作为epoll模型中红黑树的key
    }

    void loop()
    {
        while (_is_running)
        {
            int n = epoll_wait(_epoll_fd, _ev_arry, default_num, _timeout); // 默认是LT工作方式
            // 这里用户给的缓冲区如果一次不能将就绪队列中的数据取完，下次还可以拿
            switch (n)
            {
            case 0:
                LOG(INFO, "epoll timeout!\n");
                break;
            case -1:
                LOG(ERROR, "epoll error!\n");
                _is_running = false;
                break;
            default:
                LOG(INFO, "events is ready!\n");
                dispatch(n); // 事件派发
                break;
            }
        }
    }

    void startServer()
    {
        _is_running = true;
    }

    void stopServer()
    {
        _is_running = false;
    }

    ~EpollServer()
    {
        delete[] _ev_arry;
    }

private:
    unique_ptr<socket_ns::Socket> _listensocket; // 监听套接字
    uint16_t _port;                              // 服务的端口号
    int _epoll_fd;                               // epoll模型
    struct epoll_event *_ev_arry;                // 接收就绪事件的数组缓冲区
    int _timeout;                                // poll的阻塞事件，单位ms
    bool _is_running;                            // 服务是否启动
};